﻿using System;
using FLib;
using Irony.Ast;
using Irony.Parsing;
using LibraryGuard.Interfaces;

namespace LibraryGuard.Internals.AST
{
	/// <summary>
	/// Represents condition.
	/// </summary>
	internal class Condition
	{
		#region Fields
		private readonly IValue First = null;
		private readonly string Operator = string.Empty;
		private readonly IValue Second = null;
		#endregion

		#region Constructor
		public Condition(IValue first, string op, IValue second)
		{
			this.First = first;
			this.Operator = op;
			this.Second = second;
		}
		#endregion

		#region Methods
		/// <summary>
		/// Gets result of the comparison.
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		public bool GetResult(IDataProvider data)
		{
			var firstType = this.First.GetBaseType(data);
			var secondType = this.Second.GetBaseType(data);
			var type = firstType;
			if (firstType != secondType)
			{
				type = (TypeCode)Math.Min((int)firstType, (int)secondType);
				if (!this.First.CanBeConvertedTo(type, data) || !this.Second.CanBeConvertedTo(type, data))
				{
					type = (TypeCode)Math.Max((int)firstType, (int)secondType);
					if (!this.First.CanBeConvertedTo(type, data) || !this.Second.CanBeConvertedTo(type, data))
						type = TypeCode.String;
				}
			}

			switch (type)
			{
				case TypeCode.String:
					return this.CompareStrings(data);

				case TypeCode.Int32:
					return this.CompareNumbers(data);

				case TypeCode.Boolean:
					return this.CompareBools(data);
			}

			return false;
		}
		#endregion

		#region Auxiliary methods
		private bool CompareStrings(IDataProvider data)
		{
			string a = (string)this.First.GetValue(TypeCode.String, data),
				b = (string)this.Second.GetValue(TypeCode.String, data);

			switch (this.Operator)
			{
				case "==":
					return a == b;

				case "!=":
					return a != b;

				default:
					throw new ArgumentException("Operator '{0}' is not supported to compare strings.".FormatWith(this.Operator));
			}
		}

		private bool CompareNumbers(IDataProvider data)
		{
			int a = (int)this.First.GetValue(TypeCode.Int32, data),
				b = (int)this.Second.GetValue(TypeCode.Int32, data);
			return this.CompareNumbers(a, b);
		}

		private bool CompareNumbers(int a, int b)
		{
			switch (this.Operator)
			{
				case "==":
					return a == b;

				case "!=":
					return a != b;

				case "<":
					return a < b;

				case ">":
					return a > b;

				case "<=":
					return a <= b;

				case ">=":
					return a >= b;
			}
			return false;
		}

		private bool CompareBools(IDataProvider data)
		{
			int a = (bool)this.First.GetValue(TypeCode.Boolean, data) ? 1 : 0,
				b = (bool)this.Second.GetValue(TypeCode.Boolean, data) ? 1 : 0;
			return this.CompareNumbers(a, b);
		}
		#endregion

		#region Statics
		public static void Create(AstContext context, ParseTreeNode parseNode)
		{
			parseNode.AstNode = new Condition(
				(IValue)parseNode.ChildNodes[0].AstNode,
				parseNode.ChildNodes[1].ChildNodes[0].Token.Text,
				(IValue)parseNode.ChildNodes[2].AstNode
				);
		}
		#endregion
	}
}
